🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / node / src / commonTest / kotlin / org / meshtastic / feature / node / metrics / DecodePaxFromLogTest.kt
Displaying Raw • Download
feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/metrics/DecodePaxFromLogTest.kt 520fa717a938fd84682403b41f57d9f7e1ef49ef (520fa717) Text, 6.97 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.node.metrics
Tff7b72import T7ee787okio.ByteString.Companion.decodeBase64
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.common.util.nowSeconds
Tff7b72import T7ee787org.meshtastic.core.model.MeshLog
Tff7b72import T7ee787org.meshtastic.proto.Data
Tff7b72import T7ee787org.meshtastic.proto.FromRadio
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787org.meshtastic.proto.Paxcount Tff7b72as Te6edf3ProtoPaxcount
T8b949e/**
* Tests for `MetricsViewModel.decodePaxFromLog()`.
*
* Uses a minimal testable subclass to access the protected function without wiring the full ViewModel dependency graph.
*/
Tff7b72class T56d364DecodePaxFromLogTest Tb4b4b4{
T8b949e/**
* Minimal subclass that exposes `decodePaxFromLog` without requiring all ViewModel dependencies. `MetricsViewModel`
* is open, so we override with no-op constructor arguments are not needed — we only call the self-contained
* `decodePaxFromLog` method.
*/
Tff7b72private Tff7b72val Te6edf3decoder Tff7b72=
Tff7b72object Tb4b4b4{
T8b949e/** Delegates to MetricsViewModel logic extracted into a standalone helper for testing. */
Tff7b72fun Td2a8ffdecodeTb4b4b4(Te6edf3logTb4b4b4: Te6edf3MeshLogTb4b4b4)Tb4b4b4: Te6edf3ProtoPaxcount? Tff7b72= Te6edf3decodePaxFromLogStandaloneTb4b4b4(Te6edf3logTb4b4b4)
Tb4b4b4}
T8b949e// ---- Binary proto path ----
Tf0883e@Test
Tff7b72fun Td2a8ffbinaryProto_validPaxcount_decodedTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff5Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff3T79c0ff6T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3payload Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3encodeTb4b4b4(Te6edf3paxTb4b4b4)
Tff7b72val Te6edf3log Tff7b72= Te6edf3meshLogWithPacketTb4b4b4(Te6edf3payloadTb4b4b4, Te6edf3wantResponse Tff7b72= Tff7b72falseTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3wifiTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3bleTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3T79c0ff6T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3uptimeTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ffbinaryProto_wantResponse_returnsNullTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff5Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3payload Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3encodeTb4b4b4(Te6edf3paxTb4b4b4)
Tff7b72val Te6edf3log Tff7b72= Te6edf3meshLogWithPacketTb4b4b4(Te6edf3payloadTb4b4b4, Te6edf3wantResponse Tff7b72= Tff7b72trueTb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ffbinaryProto_allZeroValues_returnsNullTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff0Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff0Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3payload Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3encodeTb4b4b4(Te6edf3paxTb4b4b4)
Tff7b72val Te6edf3log Tff7b72= Te6edf3meshLogWithPacketTb4b4b4(Te6edf3payloadTb4b4b4, Te6edf3wantResponse Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ffbinaryProto_wrongPortNum_returnsNullTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff5Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3payload Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3encodeTb4b4b4(Te6edf3paxTb4b4b4)
Tff7b72val Te6edf3log Tff7b72= Te6edf3meshLogWithPacketTb4b4b4(Te6edf3payloadTb4b4b4, Te6edf3wantResponse Tff7b72= Tff7b72falseTb4b4b4, Te6edf3portNum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e// ---- Base64 fallback path ----
Tf0883e@Test
Tff7b72fun Td2a8ffbase64Fallback_validPayload_decodedTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff7Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff3Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3encodeTb4b4b4(Te6edf3paxTb4b4b4)
Tff7b72val Te6edf3base64 Tff7b72= Te6edf3okioTb4b4b4.Te6edf3ByteStringTb4b4b4.Te6edf3ofTb4b4b4(Tff7b72*Te6edf3bytesTb4b4b4)Tb4b4b4.Te6edf3base64Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3log Tff7b72= Te6edf3MeshLogTb4b4b4(Te6edf3uuid Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4, Te6edf3message_type Tff7b72= Ta5d6ff"Ta5d6ffpaxTa5d6ff"Tb4b4b4, Te6edf3received_date Tff7b72= T79c0ff0Tb4b4b4, Te6edf3raw_message Tff7b72= Te6edf3base64Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff7Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3wifiTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3bleTb4b4b4)
Tb4b4b4}
T8b949e// ---- Hex fallback path ----
T8b949e// Note: The hex path (`else if`) in the original code is unreachable for pure hex strings
T8b949e// because hex chars [0-9a-fA-F] are a strict subset of base64 chars [A-Za-z0-9+/=].
T8b949e// The base64 `if` branch always matches first. The hex fallback would only trigger for
T8b949e// strings that fail the base64 regex but pass the hex regex — which is impossible given
T8b949e// the charsets. This is documented here as a known design characteristic of decodePaxFromLog().
T8b949e// ---- Error handling ----
Tf0883e@Test
Tff7b72fun Td2a8ffinvalidRawMessage_returnsNullTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3log Tff7b72= Te6edf3MeshLogTb4b4b4(Te6edf3uuid Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4, Te6edf3message_type Tff7b72= Ta5d6ff"Ta5d6ffpaxTa5d6ff"Tb4b4b4, Te6edf3received_date Tff7b72= T79c0ff0Tb4b4b4, Te6edf3raw_message Tff7b72= Ta5d6ff"Ta5d6ffnot-valid-anything!@#Ta5d6ff"Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ffemptyLog_returnsNullTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3log Tff7b72= Te6edf3MeshLogTb4b4b4(Te6edf3uuid Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4, Te6edf3message_type Tff7b72= Ta5d6ff"Ta5d6ffpaxTa5d6ff"Tb4b4b4, Te6edf3received_date Tff7b72= T79c0ff0Tb4b4b4, Te6edf3raw_message Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3decoderTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3logTb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e// ---- Helpers ----
Tff7b72private Tff7b72fun Td2a8ffmeshLogWithPacketTb4b4b4(
Te6edf3payloadTb4b4b4: Te6edf3ByteArrayTb4b4b4,
Te6edf3wantResponseTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3portNumTb4b4b4: Te6edf3PortNum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PAXCOUNTER_APPTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3MeshLog Tb4b4b4{
Tff7b72val Te6edf3data Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3portNumTb4b4b4, Te6edf3payload Tff7b72= Te6edf3payloadTb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3want_response Tff7b72= Te6edf3wantResponseTb4b4b4)
Tff7b72val Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Te6edf3decoded Tff7b72= Te6edf3dataTb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3packetTb4b4b4)
Tff7b72return Te6edf3MeshLogTb4b4b4(
Te6edf3uuid Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4,
Te6edf3message_type Tff7b72= Ta5d6ff"Ta5d6ffpacketTa5d6ff"Tb4b4b4,
Te6edf3received_date Tff7b72= Te6edf3nowSeconds Tff7b72* T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3raw_message Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4,
Te6edf3fromRadio Tff7b72= Te6edf3fromRadioTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Standalone reimplementation of `MetricsViewModel.decodePaxFromLog()` for testing.
*
* This avoids needing to instantiate the full ViewModel with all its dependencies. The logic is identical to the
* ViewModel method.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffdecodePaxFromLogStandaloneTb4b4b4(Te6edf3logTb4b4b4: Te6edf3MeshLogTb4b4b4)Tb4b4b4: Te6edf3ProtoPaxcount? Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3logTb4b4b4.Te6edf3fromRadioTb4b4b4.Te6edf3packet
Tff7b72val Te6edf3decoded Tff7b72= Te6edf3packetTff7b72?.Te6edf3decoded
Tff7b72if Tb4b4b4(Te6edf3packet Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3decoded Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3decodedTb4b4b4.Te6edf3portnum Tff7b72=Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PAXCOUNTER_APPTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3decodedTb4b4b4.Te6edf3want_response Tff7b72=Tff7b72= Tff7b72trueTb4b4b4) Tff7b72return Tff7b72null
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3decodedTb4b4b4.Te6edf3payloadTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3paxTb4b4b4.Te6edf3ble Tff7b72!Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3paxTb4b4b4.Te6edf3wifi Tff7b72!Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3paxTb4b4b4.Te6edf3uptime Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72return Te6edf3pax
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// Swallow, fall through to alternative parsing
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3base64 Tff7b72= Te6edf3logTb4b4b4.Te6edf3raw_messageTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3base64Tb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^[A-Za-z0-9+/=Ta5d6ff\\Ta5d6ffrTa5d6ff\\Ta5d6ffn]+$Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3base64Tb4b4b4.Te6edf3okioDecodeBase64Tb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3bytesTb4b4b4)
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3base64Tb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^[0-9a-fA-F]+$Ta5d6ff"Tb4b4b4)Tb4b4b4) Tff7b72&Tff7b72& Te6edf3base64Tb4b4b4.Te6edf3length Tff7b72% T79c0ff2 Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3base64Tb4b4b4.Te6edf3chunkedTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toIntTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3bytesTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// Swallow
Tb4b4b4}
Tff7b72return Tff7b72null
Tb4b4b4}
Tff7b72private Tff7b72fun Te6edf3StringTb4b4b4.Td2a8ffokioDecodeBase64Tb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72= Tff7b72thisTb4b4b4.Te6edf3decodeBase64Tb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3toByteArrayTb4b4b4(Tb4b4b4) Tff7b72?: Te6edf3ByteArrayTb4b4b4(T79c0ff0Tb4b4b4)
Served by rngit 1.5.0 - Generated in 0.07s